﻿
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace ttbit.core
{

    public static class ShaderPool
    {
        public enum UniformType
        {
            Error,
            FLOAT,
            BOOL,
            INT,
            VEC2,
            VEC3,
            VEC4,
            IVEC2,
            IVEC3,
            IVEC4,
            MAT2,
            MAT3,
            MAT4,
            MAT2X3,
            SAMPLER1D,
            SAMPLER2D,
            SAMPLER3D,
        }
        public class ShaderObj
        {
            public string name;
            public ShaderType type;
            public int shaderid;
            public Dictionary<string, UniformType> uniforms;
        }
        public enum ShaderType
        {
            VertexShader,
            FragmentShader,
        }
        public class ShaderProgram
        {
            public string name;
            public int progid;
            public ShaderObj vs;
            public ShaderObj fs;
            public int attrid_pos;
            public int attrid_color;
            public int attrid_uv;
            public int attrid_uv2;
            public int attrid_uvpal;
            //public int uniform_color;
            public Dictionary<string, int> uniformpos;
            public UniformType GetUniformType(string key)
            {
                if (vs.uniforms.TryGetValue(key, out UniformType v))
                    return v;
                if (fs.uniforms.TryGetValue(key, out UniformType v2))
                    return v2;
                return UniformType.Error;
            }
        }


        static Dictionary<string, ShaderObj> mapvs = new Dictionary<string, ShaderObj>();
        static Dictionary<string, ShaderObj> mapfs = new Dictionary<string, ShaderObj>();
        static Dictionary<string, ShaderProgram> mapprog = new Dictionary<string, ShaderProgram>();
        public static ShaderObj AddShader(string name, ShaderType type, string code)
        {
            name = name.ToLower();

            IGL.ShaderType estype = IGL.ShaderType.VertexShader;
            Dictionary<string, ShaderObj> outmap = null;

            if (type == ShaderType.VertexShader)
            {
                estype = IGL.ShaderType.VertexShader;
                outmap = mapvs;
            }
            else if (type == ShaderType.FragmentShader)
            {
                estype = IGL.ShaderType.FragmentShader;
                outmap = mapfs;
            }
            else
                throw new Exception("error type.");
            if (outmap.ContainsKey(name))
            {
                throw new Exception("alreadyhave same shader:" + type + ":" + name);
            }

            int shader = Env.GL.CreateShader(estype);
            if (shader <= 0)
                throw new Exception("error on GL.CreateShader:" + shader);

            if (!Env.GL.CompileShader(shader, code))
            {
                string log = Env.GL.GetShaderInfoLog(shader);
                Console.Error.WriteLine("CompileShader:" + log);
                Env.GL.DeleteShader(shader);
                throw new Exception("error on CompileShader:" + name + log);
            }
            ShaderObj obj = new ShaderObj();
            obj.name = name;
            obj.type = type;
            obj.shaderid = shader;
            obj.uniforms = new Dictionary<string, UniformType>();
            FindUniform(code, obj.uniforms);
            outmap[name] = obj;
            Console.WriteLine("Add Shader:" + type + ":" + name);
            return obj;
        }
        static void FindUniform(string code, Dictionary<string, UniformType> uniforms)
        {
            var lines = code.Split("\n");
            for (var i = 0; i < lines.Length; i++)
            {
                var line = lines[i].Replace("\r", "");
                line = line.Replace("\n", "");
                line = line.Replace("\t", " ");
                var words = line.Split(new string[] { " ", ";" }, StringSplitOptions.RemoveEmptyEntries);
                var type = UniformType.Error;
                var name = "";
                var state = 0;//0 寻找uniform //1寻找type //2 寻找name
                for (var j = 0; j < words.Length; j++)
                {
                    var word = words[j];
                    if (state == 0)
                    {
                        if (word == "uniform")
                        {
                            state++;
                        }
                        else
                        {
                            break;
                        }
                    }
                    else if (state == 1)
                    {
                        if (word == "mediump" || word == "high")
                            continue;
                        if (!Enum.TryParse<UniformType>(word, true, out type))
                            throw new Exception("error uniform type.");
                        state++;
                    }
                    else if (state == 2)
                    {
                        name = word;
                        break;
                    }
                }
                if (type != UniformType.Error)
                {
                    uniforms[name] = type;
                }
            }
        }

        public static ShaderProgram LinkShader(string name, ShaderObj vs, ShaderObj fs)
        {
            if (mapprog.ContainsKey(name))
                throw new Exception("already have ShaderProgram:" + name);

            int p = Env.GL.CreateProgram();
            if (p <= 0)
                throw new Exception("error on GL.CreateProgram");
            if (vs.type != ShaderType.VertexShader || fs.type != ShaderType.FragmentShader)
                throw new Exception("error shader type.");

            if (Env.GL.LinkProgram(p, vs.shaderid, fs.shaderid) == false)
            {
                string log = Env.GL.GetProgramInfoLog(p);
                Console.Error.WriteLine("LinkProgram:" + log);
                Env.GL.DeleteProgram(p);
                throw new Exception("error on LinkProgram:" + name + " " + log);
            }
            var prog = new ShaderProgram();
            prog.name = name;
            prog.progid = p;
            prog.vs = vs;
            prog.fs = fs;
            InitProgram(prog);
            mapprog[name] = prog;
            return prog;
        }
        static void InitProgram(ShaderProgram prog)
        {
            prog.attrid_pos = Env.GL.GetAttribLocation(prog.progid, "vPosition");
            prog.attrid_uv = Env.GL.GetAttribLocation(prog.progid, "vUV");
            prog.attrid_uv2 = Env.GL.GetAttribLocation(prog.progid, "vUV2");
            prog.attrid_uvpal = Env.GL.GetAttribLocation(prog.progid, "vUVPal");
            prog.attrid_color = Env.GL.GetAttribLocation(prog.progid, "vColor");

            //以后可以把常用的uniforms 和 通道都固定
            prog.uniformpos = new Dictionary<string, int>();
            foreach (var u in prog.vs.uniforms)
            {
                int upos = Env.GL.GetUniformLocation(prog.progid, u.Key);
                //if (u.Key == "color")
                //    prog.uniform_color = upos;
                //else
                prog.uniformpos[u.Key] = upos;

            }

            foreach (var u in prog.fs.uniforms)
            {
                int upos = Env.GL.GetUniformLocation(prog.progid, u.Key);
                //if (u.Key == "color")
                //    prog.uniform_color = upos;
                //else
                prog.uniformpos[u.Key] = upos;
            }

        }
        public static ShaderProgram GetShader(string name)
        {
            if(mapprog.TryGetValue(name,out var v))
            {
                return v;
            }
            return null;
        }
        public static ShaderObj GetVS(string name)
        {
            if (mapvs.TryGetValue(name, out var v))
            {
                return v;
            }
            return null;

        }
        public static ShaderObj GetFS(string name)
        {
            if (mapfs.TryGetValue(name, out var v))
            {
                return v;
            }
            return null;

        }
        //public static List<ShaderProgram> LoadByJson(string filename)
        //{
        //    List<ShaderProgram> progs = new List<ShaderProgram>();
        //    var code = System.IO.File.ReadAllText(filename);

        //    var infos = Newtonsoft.Json.Linq.JArray.Parse(code);
        //    foreach (JObject obj in infos)
        //    {
        //        var name = obj["name"].ToString();
        //        var vs = obj["vs"].ToString();
        //        var fs = obj["fs"].ToString();
        //        ShaderObj vsobj = null;
        //        ShaderObj fsobj = null;
        //        if (vs.IndexOf("file:") == 0)
        //        {
        //            var _path = System.IO.Path.GetDirectoryName(filename);
        //            var _file = System.IO.Path.Combine(_path, vs.Substring(5));
        //            var _code = System.IO.File.ReadAllText(_file);
        //            vsobj = AddShader(name, ShaderType.VertexShader, _code);
        //        }
        //        else
        //        {
        //            vsobj = mapvs[vs];
        //        }
        //        if (fs.IndexOf("file:") == 0)
        //        {
        //            var _path = System.IO.Path.GetDirectoryName(filename);
        //            var _file = System.IO.Path.Combine(_path, fs.Substring(5));
        //            var _code = System.IO.File.ReadAllText(_file);
        //            fsobj = AddShader(name, ShaderType.FragmentShader, _code);
        //        }
        //        else
        //        {
        //            fsobj = mapfs[vs];
        //        }
        //        progs.Add(LinkShader(name, vsobj, fsobj));
        //    }
        //    return progs;
        //}
    }


}
